#include "jionotifier_p.h"
#if defined(J_OS_UNIX) || defined(J_OS_LINUX)

#include <cstring>
#include <unistd.h>
#include <sys/epoll.h>

namespace JXQ
{

#define LISTENTHREAD_ERROR() \
	jError() << "JIONotifier: ListenThread:" << __LINE__ << ":"

#define MAX_EVENTS  64

static int gEpollFd = 0;

static std::mutex gMutex;
static std::unordered_map<int, JIONotifier*> gNotifierHash;

ListenThread::ListenThread()
{
	detach();
	gEpollFd = epoll_create(MAX_EVENTS);
	if( gEpollFd < 0 )
		LISTENTHREAD_ERROR() << "epoll_create() failed:" << strerror(errno);
}

void ListenThread::addDescriptor(JIONotifier *q_ptr, int descriptor, Type type)
{
	if( gEpollFd <= 0 )
		return ;

	epoll_event event = {EPOLLET, {}};

	if( type & JIONotifier::Read )
		event.events |= EPOLLIN;

	if( type & JIONotifier::Write )
		event.events |= EPOLLOUT;

	event.data.fd = descriptor;
	int res = epoll_ctl(gEpollFd, EPOLL_CTL_ADD, descriptor, &event);

	if( res < 0 )
	{
		LISTENTHREAD_ERROR() << "epoll_ctl() failed:" << strerror(errno);
		return ;
	}

	gMutex.lock();
	gNotifierHash.emplace(descriptor, q_ptr);
	gMutex.unlock();
}

void ListenThread::deleteDescriptor(int fd)
{
	if( gEpollFd <= 0 )
		return ;

	J_MUTEX_LOCKER(gMutex);

	auto it = gNotifierHash.find(fd);
	if( it == gNotifierHash.end() )
		return ;

	gNotifierHash.erase(fd);
	J_MUTEX_LOCKER_UNLOCK();

	struct epoll_event event;
	int res = epoll_ctl(gEpollFd, EPOLL_CTL_DEL, fd, &event);

	if( res < 0 )
		LISTENTHREAD_ERROR() << "epoll_ctl() failed:" << strerror(errno);
}

void ListenThread::run()
{
	int res = 0;

	struct epoll_event gRevents[MAX_EVENTS];

	for(;;)
	{
		do {
			res = epoll_wait(gEpollFd, gRevents, MAX_EVENTS, -1);
		}
		while( res == -EINTR );

		if( res < 0 )
		{
			LISTENTHREAD_ERROR() << "epoll_wait() failed:" << strerror(errno);
			return ;
		}

		for(int i=0; i<res; i++)
		{
			Type type;
			if( gRevents[i].events & EPOLLIN )
				type |= JIONotifier::Read;

			if( gRevents[i].events & EPOLLOUT )
				type |= JIONotifier::Write;

			gMutex.lock();

			auto it = gNotifierHash.find(gRevents[i].data.fd);
			if( it != gNotifierHash.end() )
			{
				auto *notifier = (*it).second;
				notifier->event(type);
				notifier->ready.send(gRevents[i].data.fd, type);
			}

			gMutex.unlock();
		}
	}
}

void JIONotifierPrivate::close()
{
	if( m_closeOnDelete )
		::close(m_descriptor);
}

} //namespace JXQ


#endif //unix/linux
